package com.allwees.bs.c.module.security;

import com.alibaba.fastjson.JSONObject;
import com.allwees.bs.c.common.auth.constant.EAuthType;
import com.allwees.bs.c.module.user.dto.UserDto;
import com.allwees.bs.c.module.user.entity.UserEntity;
import com.allwees.bs.core.model.constant.CacheName;
import com.allwees.bs.core.model.user.context.CurrentUser;
import com.allwees.bs.core.modelbase.vo.R;
import com.allwees.core.cache.RedisUtil;
import com.allwees.core.common.constant.EPlatform;
import com.allwees.core.common.util.UuidUtil;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.crypto.SecretKey;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;

/**
 *   生成jwt token工具类
 *   <p>
 *       <ol>
 *           <li>根据不同的终端生成不同有效时长的jwt；</li>
 *           <li>把该token(或唯一标志)缓存，当用户退出、修改/重置密码、修改邮箱、token过期后均需做删除处理</li>
 *           <li>把生成的token以json的格式响应给前端(最大程度兼容之前的版本)</li>
 *       </ol>
 * @author Daniel
 * @version 1.0.0
 * @since 2020/11/26 11:29
 */
@Slf4j
@Component
public class JwtTokenHelper {

    public static final String ACCESS_TOKEN = "access_token";

    public static final String TOKEN_TYPE = "token_type";

    public static final String CLAIM_NAME_UUUID = "uuuid";

    public static final String CLAIM_NAME_TOKEN_UUID = "tuuid";

    public static final String CLAIM_NAME_CHANEL = "chanel";

    @Resource
    private JwtTokenConfig jwtTokenConfig;

    private SecretKey secretKey;

    @PostConstruct
    private void init() {
        byte[] keyBytes = Decoders.BASE64.decode(jwtTokenConfig.getSecret());
        this.secretKey = Keys.hmacShaKeyFor(keyBytes);
    }

    /**
     * 正常登录用户生成jwt
     * @param authentication
     * @param sourceType
     * @return
     */
    public R genResult(Authentication authentication,String sourceType){
        CurrentUser currentUser = (CurrentUser)authentication.getPrincipal();
        //返回token(最大程度兼容以前的版本)
        return R.ok()
                .put(JwtTokenHelper.ACCESS_TOKEN, genJwtToken(currentUser.getUsername(), currentUser.getUserUuid(),sourceType))
                .put(JwtTokenHelper.TOKEN_TYPE, jwtTokenConfig.getPrefix().trim());
    }

    /**
     * 三方登录用户生成jwt
     * @param user
     * @param sourceType
     * @return
     */
    public R genResult(UserEntity user, EAuthType sourceType){
        //facebook可能没有email，需要用三方账号的id
        String username = user.getIdByAuthType(sourceType);
        return R.ok()
                .put(JwtTokenHelper.ACCESS_TOKEN, genJwtToken(username,user.getUuid(),sourceType.name()))
                .put(JwtTokenHelper.TOKEN_TYPE, jwtTokenConfig.getPrefix().trim());
    }

    /**
     * 游客注册账号后直接生成token
     * @param user
     * @param platform
     * @return
     */
    public R genResult(UserDto user, EPlatform platform){
        JSONObject json = new JSONObject();
        json.put(JwtTokenHelper.ACCESS_TOKEN, genJwtToken(user.getEmail(), user.getUuid(), platform.name()));
        json.put(JwtTokenHelper.TOKEN_TYPE, jwtTokenConfig.getPrefix().trim());
        JSONObject result = new JSONObject();
        result.put("user", json);
        return R.ok(result);
    }

    /**
     * 生成jwt token
     * @param username
     * @param userUuid
     * @param sourceType
     * @return
     */
    private String genJwtToken(String username,String userUuid,String sourceType) {
//        String authorities = authentication.getAuthorities().stream()
//                .map(GrantedAuthority::getAuthority)
//                .collect(Collectors.joining(","));
        //根据不同的sourceType生成不同时长的token
        log.info("===>generate jwt token,sourceType:{},username:{},userUuid:{}", sourceType,username,userUuid);
        Calendar calendar = Calendar.getInstance();
        Date now = calendar.getTime();
        calendar.add(Calendar.SECOND, getExpiration(sourceType));
        Date expiration = calendar.getTime();
        //缓存(覆盖)token
        return Jwts.builder()
                .setSubject(username)
                .claim(CLAIM_NAME_UUUID, userUuid)
                .claim(CLAIM_NAME_TOKEN_UUID, UuidUtil.generate())
                .signWith(secretKey, SignatureAlgorithm.HS512)
                .setExpiration(expiration)
                .setIssuedAt(now)
                .compact();
    }

    public Claims parseClaims(String token) {
        try {
            return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody();
        } catch (ExpiredJwtException e) {
            throw e;
        } catch (Exception e) {
            log.error(">>> parse claims error:{}", e.getMessage(), e);
            return null;
        }
    }

    public UsernamePasswordAuthenticationToken genFromClaims(Claims claims) {
        CurrentUser currentUser = new CurrentUser(
                claims.get(CLAIM_NAME_UUUID).toString(),
                claims.getSubject(),
                "",
                Collections.emptyList());
        currentUser.setTokenUuid(claims.get(CLAIM_NAME_TOKEN_UUID).toString());
        currentUser.setExpiredAt(claims.getExpiration().getTime());
        return new UsernamePasswordAuthenticationToken(currentUser, null, null);
    }

    /**
     * 退出、修改/重置密码、修改邮箱需要强制token失效
     * @param currentUser
     */
    public void invalidateToken(CurrentUser currentUser){
        String userUuid = currentUser.getUserUuid();
        String tokenUuid = currentUser.getTokenUuid();
        String key = String.format(CacheName.USER_TOKEN_IVALID_PATTERN,userUuid,tokenUuid);
        long ttlInMillis = currentUser.getExpiredAt() - System.currentTimeMillis();
        if (ttlInMillis <= 0) {
            return;
        }
        long ttlInSecs = (ttlInMillis) / 1000 + 1;
        RedisUtil.set(key, "1", ttlInSecs);
    }

    /**
     * 验证是否是失效的token，若是需要重新登录获取token
     * @param claims
     * @return
     */
    public boolean isTokenInvalid(Claims claims) {
        String userUuid = claims.get(CLAIM_NAME_UUUID).toString();
        String tokenUuid = claims.get(CLAIM_NAME_TOKEN_UUID).toString();
        return RedisUtil.hasKey(String.format(CacheName.USER_TOKEN_IVALID_PATTERN,userUuid,tokenUuid));
    }

    private int getExpiration(String sourceType){
        EPlatform ePlatform = EPlatform.parseFrom(sourceType);
        switch (ePlatform) {
            case IOS:
            case ANDROID:
                return jwtTokenConfig.getAppExpiration();
            default:
                return jwtTokenConfig.getPcExpiration();
        }
    }
}
